"""
测试工作流引擎的变量替换功能
"""
import pytest
from unittest.mock import Mock, MagicMock
from services.workflow_engine import WorkflowEngine
from models.workflow_instance import WorkflowInstance


class TestVariableReplacement:
    """测试变量替换功能"""

    @pytest.fixture
    def mock_db(self):
        """模拟数据库会话"""
        return Mock()

    @pytest.fixture
    def engine(self, mock_db):
        """创建工作流引擎实例"""
        return WorkflowEngine(mock_db)

    @pytest.fixture
    def mock_instance(self):
        """模拟工作流实例"""
        instance = Mock(spec=WorkflowInstance)
        instance.id = 1
        instance.instance_name = "test-workflow"
        return instance

    def test_simple_field_access(self, engine, mock_instance):
        """测试简单字段访问"""
        node_outputs = {
            "nodeA": {"experimentId": "exp_123"}
        }
        config = {"input": "{{nodeA.experimentId}}"}

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["input"] == "exp_123"

    def test_nested_field_access(self, engine, mock_instance):
        """测试嵌套字段访问"""
        node_outputs = {
            "nodeA": {
                "versionInfo": {
                    "name": "experiment-001"
                }
            }
        }
        config = {"name": "{{nodeA.versionInfo.name}}"}

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["name"] == "experiment-001"

    def test_array_index_access(self, engine, mock_instance):
        """测试数组索引访问"""
        node_outputs = {
            "nodeA": {
                "versionInfo": {
                    "outputs": [
                        {"obsUrl": "obs://path/to/data", "type": "result"}
                    ]
                }
            }
        }
        config = {"path": "{{nodeA.versionInfo.outputs[0].obsUrl}}"}

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["path"] == "obs://path/to/data"

    def test_array_multiple_elements(self, engine, mock_instance):
        """测试访问数组中的多个元素"""
        node_outputs = {
            "nodeA": {
                "versionInfo": {
                    "outputs": [
                        {"obsUrl": "obs://path/1", "type": "checkpoint"},
                        {"obsUrl": "obs://path/2", "type": "result"}
                    ]
                }
            }
        }
        config = {
            "checkpoint": "{{nodeA.versionInfo.outputs[0].obsUrl}}",
            "result": "{{nodeA.versionInfo.outputs[1].obsUrl}}"
        }

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["checkpoint"] == "obs://path/1"
        assert result["result"] == "obs://path/2"

    def test_builtin_variables(self, engine, mock_instance):
        """测试内置变量"""
        config = {
            "workflow_name": "{{workflow.name}}",
            "workflow_id": "{{workflow.id}}"
        }

        result = engine._replace_variables(config, mock_instance, {})
        assert result["workflow_name"] == "test-workflow"
        assert result["workflow_id"] == "1"

    def test_backward_compatibility_output_prefix(self, engine, mock_instance):
        """测试向后兼容旧格式 {{nodeId.output.field}}"""
        node_outputs = {
            "nodeA": {"experimentId": "exp_123"}
        }
        config = {"input": "{{nodeA.output.experimentId}}"}

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["input"] == "exp_123"

    def test_missing_node_reference(self, engine, mock_instance):
        """测试引用不存在的节点"""
        node_outputs = {}
        config = {"input": "{{nonexistent.field}}"}

        # 应该保持原样，不抛出异常
        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["input"] == "{{nonexistent.field}}"

    def test_missing_field_path(self, engine, mock_instance):
        """测试引用不存在的字段路径"""
        node_outputs = {
            "nodeA": {"data": {"field1": "value1"}}
        }
        config = {"input": "{{nodeA.data.nonexistent}}"}

        # 路径不存在，应该保持原样
        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["input"] == "{{nodeA.data.nonexistent}}"

    def test_complex_nested_structure(self, engine, mock_instance):
        """测试复杂的嵌套结构"""
        node_outputs = {
            "nodeA": {
                "versionId": "abc123",
                "romaStatusCode": "10",
                "versionInfo": {
                    "name": "experiment-001",
                    "status": "completed",
                    "outputs": [
                        {
                            "obsUrl": "obs://bucket/path/to/output",
                            "type": "result",
                            "size": 1024000
                        },
                        {
                            "obsUrl": "obs://bucket/path/to/checkpoint",
                            "type": "checkpoint",
                            "size": 512000
                        }
                    ],
                    "metrics": {
                        "accuracy": 0.95,
                        "loss": 0.05
                    }
                }
            }
        }
        config = {
            "version_id": "{{nodeA.versionId}}",
            "output_path": "{{nodeA.versionInfo.outputs[0].obsUrl}}",
            "checkpoint_path": "{{nodeA.versionInfo.outputs[1].obsUrl}}",
            "accuracy": "{{nodeA.versionInfo.metrics.accuracy}}"
        }

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["version_id"] == "abc123"
        assert result["output_path"] == "obs://bucket/path/to/output"
        assert result["checkpoint_path"] == "obs://bucket/path/to/checkpoint"
        assert result["accuracy"] == "0.95"

    def test_multiple_variables_in_string(self, engine, mock_instance):
        """测试字符串中包含多个变量"""
        node_outputs = {
            "nodeA": {"experimentId": "exp_123"},
            "nodeB": {"dataPath": "obs://data"}
        }
        config = {
            "message": "Experiment {{nodeA.experimentId}} saved to {{nodeB.dataPath}}"
        }

        result = engine._replace_variables(config, mock_instance, node_outputs)
        assert result["message"] == "Experiment exp_123 saved to obs://data"


class TestExtractValueByJsonpath:
    """测试 JSONPath 值提取功能"""

    @pytest.fixture
    def mock_db(self):
        return Mock()

    @pytest.fixture
    def engine(self, mock_db):
        return WorkflowEngine(mock_db)

    def test_simple_field(self, engine):
        """测试简单字段提取"""
        data = {"field": "value"}
        result = engine._extract_value_by_jsonpath(data, "field")
        assert result == "value"

    def test_nested_field(self, engine):
        """测试嵌套字段提取"""
        data = {"parent": {"child": "value"}}
        result = engine._extract_value_by_jsonpath(data, "parent.child")
        assert result == "value"

    def test_array_index(self, engine):
        """测试数组索引"""
        data = {"items": ["first", "second", "third"]}
        result = engine._extract_value_by_jsonpath(data, "items[0]")
        assert result == "first"

    def test_nested_array(self, engine):
        """测试嵌套数组"""
        data = {
            "results": [
                {"name": "item1", "value": 10},
                {"name": "item2", "value": 20}
            ]
        }
        result = engine._extract_value_by_jsonpath(data, "results[1].name")
        assert result == "item2"

    def test_nonexistent_field(self, engine):
        """测试不存在的字段"""
        data = {"field1": "value1"}
        result = engine._extract_value_by_jsonpath(data, "field2")
        assert result is None

    def test_invalid_array_index(self, engine):
        """测试无效的数组索引"""
        data = {"items": ["first", "second"]}
        result = engine._extract_value_by_jsonpath(data, "items[10]")
        assert result is None


class TestVariableValidation:
    """测试变量引用验证功能"""

    @pytest.fixture
    def mock_db(self):
        return Mock()

    @pytest.fixture
    def engine(self, mock_db):
        return WorkflowEngine(mock_db)

    def test_valid_workflow(self, engine):
        """测试合法的工作流"""
        workflow_def = {
            "nodes": [
                {
                    "id": "nodeA",
                    "type": "create_experiment",
                    "config": {}
                },
                {
                    "id": "nodeB",
                    "type": "data_processing",
                    "config": {
                        "input": "{{nodeA.versionId}}"
                    }
                }
            ]
        }

        valid, errors = engine._validate_variable_references(workflow_def)
        assert valid is True
        assert len(errors) == 0

    def test_nonexistent_node_reference(self, engine):
        """测试引用不存在的节点"""
        workflow_def = {
            "nodes": [
                {
                    "id": "nodeA",
                    "type": "create_experiment",
                    "config": {
                        "input": "{{nonexistent.field}}"
                    }
                }
            ]
        }

        valid, errors = engine._validate_variable_references(workflow_def)
        assert valid is False
        assert len(errors) == 1
        assert "不存在的节点" in errors[0]

    def test_circular_dependency(self, engine):
        """测试循环依赖"""
        workflow_def = {
            "nodes": [
                {
                    "id": "nodeA",
                    "type": "create_experiment",
                    "config": {
                        "input": "{{nodeB.output}}"
                    }
                },
                {
                    "id": "nodeB",
                    "type": "data_processing",
                    "config": {
                        "input": "{{nodeA.output}}"
                    }
                }
            ]
        }

        valid, errors = engine._validate_variable_references(workflow_def)
        assert valid is False
        assert any("循环依赖" in error for error in errors)

    def test_builtin_variables_allowed(self, engine):
        """测试内置变量是合法的"""
        workflow_def = {
            "nodes": [
                {
                    "id": "nodeA",
                    "type": "create_experiment",
                    "config": {
                        "name": "{{workflow.name}}",
                        "timestamp": "{{timestamp}}"
                    }
                }
            ]
        }

        valid, errors = engine._validate_variable_references(workflow_def)
        assert valid is True
        assert len(errors) == 0
